package steampunk.entities;

import java.util.Hashtable;

import steampunk.GameVars;
import steampunk.art.Art;
import steampunk.geom.Geom;
import steampunk.geom.Intersectable;
import steampunk.utils.Mutil;

/**
 * Represents a game entity. Subclasses should (at the very least) set
 * the <i>geom</i> and <i>art</i> variables to something useful.
 * @author garrett
 *
 */
public abstract class Entity implements Geom, Art {
	// need to be protected so subclasses can get at it
	/** The geometry (physical representation) of this Entity */
	Geom geom;
	/** The art (graphical representation) of this Entity */
	Art art;
	/** Whether other entities can hit this */
	private boolean solid = true;
	/** Whether this entity is part of the background */
	private boolean background = false;
	/** Whether the collision engine should check if anything is colliding with this
	 * (The distinction between this variable and <i>solid</i> is that nothing will
	 * collided with something that isn't solid, but something that doesn't test
	 * for collisions can still be hit with something else)
	 */
	private boolean testcollisions = false;

	/**
	 * If this flag is set to true, the object will be promptly deleted.
	 */
	private boolean shouldRemove = false;
	
	private boolean isStatic = false;
	
	private Hashtable<String, EAction> actionTable = new Hashtable<String, EAction>();
	
	private int zlevel = 0;
	
	public Entity() {
		// default Z-Level for all entities
		setZLevel(2);
		
		// build default action table
		
		putAction("jump", new EAction() {
			public boolean act(Entity actOn, Object[] args) {
				double jumpHeight = 50;
				// You say "jump", I say "how high?"
				if (args != null && args.length > 0)
					jumpHeight = ((Number)args[0]).doubleValue();
				double requiredSpeed = Mutil.getVoFor(-jumpHeight, GameVars.getInstance().GRAVITY);
				actOn.setVelY(requiredSpeed);
				return true;
			}
			public void deact(Entity actOn) {
				// nada
			}
		});
		
		putAction("left", new EAction() {
			public boolean act(Entity actOn, Object[] args) {
				double speed = 300;
				if (args != null && args.length > 0)
					speed = ((Number)args[0]).doubleValue();
				actOn.setVelX(-speed);
				setAnimation("left");
				return true;
			}
			public void deact(Entity actOn) {
				if (actOn.getVelX() < 0)
					actOn.setVelX(0);
				setAnimation("idle");
			}
		});
		
		putAction("right", new EAction() {
			public boolean act(Entity actOn, Object[] args) {
				double speed = 300;
				if (args != null && args.length > 0)
					speed = ((Number)args[0]).doubleValue();
				actOn.setVelX(speed);
				setAnimation("right");
				return true;
			}
			public void deact(Entity actOn) {
				if (actOn.getVelX() > 0)
					actOn.setVelX(0);
				setAnimation("idle");
			}
		});
	}
	
	/**
	 * Sets the current animation to the given animation, if
	 * it exists. Subclasses do not have to override this
	 * if it isn't applicable.
	 * @param name
	 */
	public void setAnimation(String name) {
		
	}
	
	/**
	 * Sets the depth of this object on the screen (positive is into the screen)
	 * @param z
	 */
	public void setZLevel(int z) {
		this.zlevel = z;
	}
	/**
	 * Returns the depth of this object ont he screen (positive is into the screen)
	 * @return
	 */
	public int getZLevel() {
		return this.zlevel;
	}
	
	/**
	 * Called to notify the entity that its collision loop has begun
	 */
	public void collisionTestStart() {
		
	}
	/**
	 * Called to notify the entity that its collision loop has ended
	 */
	public void collisionTestEnd() {
		
	}
	
	/**
	 * Adds the command, action pair to the list of actions that
	 * can be performed. This will overwrite any existing action
	 * that has the same command key.
	 * @param command
	 * @param action
	 */
	public void putAction(String command, EAction action) {
		if (action == null) {
			if (actionTable.containsKey(command))
				actionTable.remove(command);
		} else {
			actionTable.put(command, action);
		}
	}
	
	/**
	 * Returns true if this entity has code for the
	 * given action.
	 * @param command
	 * @return
	 */
	public boolean hasAction(String command) {
		return actionTable.containsKey(command);
	}
	
	/** Triggers the action with the given key
	 * <br>
	 * Eg, triggerAction("jump") might make an entity jump if
	 * it is an entity that jumping makes sense for.
	 *  */
	public void triggerAction(String command) {
		triggerAction(command, null);
	}
	
	/**
	 * Deactivates the given action.
	 * @param command
	 */
	public void untriggerAction(String command) {
		if (hasAction(command)) {
			actionTable.get(command).deact(this);
		}
	}
	
	/**
	 * Triggers the action with the given key and parameters<br>
	 * Eg, triggerAction("jump", 40) might make an entity jump 40 pixels high.
	 * @param command
	 * @param args
	 */
	public void triggerAction(String command, Object[] args) {
		if (hasAction(command)) {
			actionTable.get(command).act(this, args);
		}
	}
	
	/**
	 * Performs collision behavior. Should be overridden by subclasses.
	 * @param other
	 */
	protected boolean collideAction(Entity other) {
		return false;
	}
	
	/**
	 * Helper collision function to prevent infinite loops
	 * @param other
	 * @return
	 */
	protected boolean collideWithMe(Entity other) {
		return collideAction(other);
	}
	
	/**
	 * Simulates a collision with the other entity.
	 * an infinite loop).
	 * @param other
	 * @return
	 */
	public boolean collide(Entity other) {
		boolean result = collideAction(other);
		other.collideWithMe(this);
		return result;
	}
	
	/**
	 * If true, the collision engine should check to see
	 * if any other Entities are colliding with this one
	 * @return
	 */
	public boolean shouldTestCollisions() {
		return testcollisions;
	}
	
	/**
	 * Sets whether the collision engine checks all the other
	 * solid entities to see if they collide with this
	 * @param b
	 */
	protected void setTestCollisions(boolean b) {
		testcollisions = b;
	}
	
	/**
	 * Returns true if this object should be deleted
	 * @return
	 */
	public boolean shouldRemove() {
		return shouldRemove;
	}
	
	/**
	 * Marks this object for deletion
	 */
	public void flagForRemoval() {
		shouldRemove = true;
	}
	
	/**
	 * If an entity is static, it remains
	 * stationary (it doesn't move around). This
	 * is used to optimize collision detection.
	 * @return
	 */
	public boolean isStatic() {
		return isStatic;
	}
	/**
	 * Sets whether the object is static (remains
	 * stationary on the screen). This is used
	 * to optimize collision detection.
	 * @param b
	 */
	public void setStatic(boolean b) {
		this.isStatic = b;
	}
	
	/**
	 * Returns true if this object can be hit
	 * by something
	 * @return
	 */
	public boolean isSolid() {
		return solid;
	}
	/**
	 * Sets whether this object can be collided with
	 * @param b
	 */
	protected void setSolid(boolean b) {
		this.solid = b;
	}

	/**
	 * Updates this entity's art and physics.
	 * @param seconds
	 */
	public void update(double seconds) {
		geom.updatePosition(seconds);
		art.updateArt(seconds);
		extraUpdate(seconds);
	}
	
	/**
	 * Extra processing used by subclasses. Should call
	 * super.extraUpdate(seconds) as the first call if
	 * it is a subclass of a subclass.
	 * @param seconds
	 */
	protected void extraUpdate(double seconds) {
		
	}
	
	
	// Pass inherited methods to my Art instance
	public void draw(java.awt.Graphics2D g) {
		if (art == null)
			art = geom.getWireArt();
		art.draw(g);
	}
	public void updateArt(double seconds) {
		if (art == null)
			art = geom.getWireArt();
		art.updateArt(seconds);
	}
	

	// Pass inherited methods to my Geom instance
	public void hardSetX(double d) {
		geom.hardSetX(d);
	}
	public void hardSetY(double d) {
		geom.hardSetY(d);
	}
	
	public void updatePosition(double seconds) {
		geom.updatePosition(seconds);
	}
	
	public double centerX() {
		return geom.centerX();
	}
	public double centerY() {
		return geom.centerY();
	}
	public void centerX(double d) {
		geom.centerX(d);
	}
	public void centerY(double d) {
		geom.centerY(d);
	}
	
	public double lastLeft() {
		return geom.lastLeft();
	}
	
	public double lastRight() {
		return geom.lastRight();
	}
	
	public double lastTop() {
		return geom.lastTop();
	}
	
	public double lastBottom() {
		return geom.lastBottom();
	}
	
	public double lastVelX() {
		return geom.lastVelX();
	}
	
	public double lastVelY() {
		return geom.lastVelY();
	}
	
	public double dx() {
		return geom.dx();
	}
	
	public double dy() {
		return geom.dy();
	}
	
	public double left() {
		return geom.left();
	}
	public double right() {
		return geom.right();
	}
	public double top() {
		return geom.top();
	}
	public double bottom() {
		return geom.bottom();
	}
	
	public void left(double d) {
		geom.left(d);
	}
	public void right(double d) {
		geom.right(d);
	}
	public void top(double d) {
		geom.top(d);
	}
	public void bottom(double d) {
		geom.bottom(d);
	}
	
	public int ix() {
		return geom.ix();
	}
	public int iy() {
		return geom.iy();
	}
	
	public void setX(double d) {
		geom.setX(d);
	}
	public void setY(double d) {
		geom.setY(d);
	}
	public double getX() {
		return geom.getX();
	}
	public double getY() {
		return geom.getY();
	}
	
	public void moveTo(double x, double y) {
		geom.moveTo(x, y);
	}
	public void moveBy(double x, double y) {
		geom.moveBy(x, y);
	}
	
	public void setVelX(double x) {
		geom.setVelX(x);
	}
	public void setVelY(double y) {
		geom.setVelY(y);
	}
	
	public double getVelX() {
		return geom.getVelX();
	}
	public double getVelY() {
		return geom.getVelY();
	}
	
	public void accelerate(double x, double y) {
		geom.accelerate(x, y);
	}
	
	public void decelerate(double x, double y) {
		geom.decelerate(x, y);
	}
	
	public double lastX() {
		return geom.lastX();
	}
	public double lastY() {
		return geom.lastY();
	}
	
	
	// pass Intersectable methods to Geom instance
	public boolean intersects(Intersectable other) {
		return geom.intersects(other);
	}
	public boolean intersectsLine(double x1, double y1, double x2, double y2) {
		return geom.intersectsLine(x1, y1, x2, y2);
	}
	public boolean containsPoint(double x, double y) {
		return geom.containsPoint(x, y);
	}
	public boolean intersectsCircle(double x, double y, double radius) {
		return geom.intersectsCircle(x, y, radius);
	}
	public boolean intersectsRect(int x, int y, int w, int h) {
		return geom.intersectsRect(x, y, w, h);
	}
	
	public Art getWireArt() {
		return geom.getWireArt();
	}
}
